import { Check, deepClone, paramsToObj, paramsToUrl } from './util'

// uni 方法
// 修复vue3打包后 uni['navigateTo']访问不到，所以需要直接保存函数引用
const UNI_NAV_ACTION = {
  navigateTo: {
    // 保留当前页面，跳转到应用内的某个页面，使用navigateBack可以返回到原页面。
    action: uni.navigateTo,
    alias: ['to', 'push'],
  },
  redirectTo: {
    // 关闭当前页面，跳转到应用内的某个页面。 跳转到 tabBar 页面只能使用 switchTab 跳转
    action: uni.redirectTo,
    alias: ['replace'],
  },
  reLaunch: {
    // 关闭所有页面，打开到应用内的某个页面。
    action: uni.reLaunch,
    alias: [],
  },
  switchTab: {
    // 跳转到 tabBar 页面，并关闭其他所有非 tabBar 页面
    action: uni.switchTab,
    alias: [],
  },
  navigateBack: {
    // 返回页面
    action: uni.navigateBack,
    alias: ['back'],
  },
}

function _nav(args) {
  const { type = 'navigateTo', url = '', delta = '', query = {}, uniConfig = {}, encode, } = args
  let queryUrl
  if (encode * 1 === 1) {
    const queryStr = encodeURIComponent(JSON.stringify(query))
    // 跳转tab页不能携带参数，只能纯路径
    queryUrl = Check.isPlainObject(query) ? url : `${url}${url.includes('?') ? '&' : '?'}qryStr=${queryStr}`
  }
  else {
    // 跳转tab页不能携带参数，只能纯路径
    queryUrl = Check.isPlainObject(query) ? url : `${url}${url.includes('?') ? '&' : '?'}encode=0&${paramsToUrl(query)}`
  }

  return new Promise((resolve, reject) => {
    const config = {
      ...uniConfig,
      success: (res) => resolve({
        config: args,
        ...res,
      }),
      // eslint-disable-next-line prefer-promise-reject-errors
      fail: (err) => reject({
        config: args,
        errCode: 400,
        ...err,
      })
    }
    delta ? (config.delta = delta) : (config.url = queryUrl)
    UNI_NAV_ACTION[type].action(config)
  })
}

/**
 * @param {string} fullPath 全路径 pages/home/index
 * @param {string} relative 相对路径./index2
 * @return {string} 根据全路径返回当前相对路径的完整路径 输入../index2  返回类似 /pages/home/index2
 */
function getFullPath(relative, fullPath) {
  if (relative.startsWith('/')) return relative
  if (!relative.startsWith('.')) return `/${relative}`

  const stack = fullPath.split('/')
  const parts = relative.split('/')
  stack.pop() // 移除当前项 (或者空字符)
  for (let i = 0; i < parts.length; i++) {
    if (parts[i] === '.') continue
    if (parts[i] === '..') stack.pop()
    else stack.push(parts[i])
  }
  return `/${stack.join('/')}`
}

const PAGE_CUSTOM_KEY = Symbol('PAGE_CUSTOM_KEY')

class Router {
  constructor() {
    this.isLock = false
    this.isEncode = true
    this.pageParams = new Map()
    this.paramsKey = ''
    // 拦截器
    this.before = null
    this.after = null
    this.onError = null

    Object.keys(UNI_NAV_ACTION).forEach((method) => {
      const func = (userConfig, uniConfig = {}) => this._push(userConfig, uniConfig, method)
      Router.prototype[method] = func
      UNI_NAV_ACTION[method].alias.forEach((alia) => {
        Router.prototype[alia] = func
      })
    })
  }

  getPageStack() {
    // eslint-disable-next-line no-undef
    return getCurrentPages()
  }

  _getCurrentPage(current = 1, routers = this.getPageStack()) {
    if (routers.length > 0) {
      return routers[Math.max(0, routers.length - current)]
    }
    throw new Error('未发现页面堆栈')
  }

  currentPage() {
    return this._getCurrentPage()
  }

  /**
   * @param {object | number | Sting} userConfig 传url,query,params 路径和参数,query类似get，params类似post。刷新丢失
   * @param {object} uniConfig 额外配置，配置同uni原生跳转的其它参数
   * @return {promise}
   */
  async _push(userConfig, uniConfig = {}, type, eventData) {
    if (this.isLock) return
    const routers = this.getPageStack()
    this.isLock = true
    let queryObj = {}
    let paramsObj = {}
    let eventObj = null
    let url
    let name
    let deltaNums

    switch (true) {
      case Check.isObject(userConfig): {
        const { params = {}, query = {}, on, delta, encode = false } = userConfig
        this.isEncode = encode || this.isEncode
        name = userConfig.name
        if (!Check.isObject(params) || !Check.isObject(query)) {
          throw new TypeError('params数据必须是Object')
        }
        if (!delta) {
          url = userConfig.url
          queryObj = query
          paramsObj = params
          if (on && typeof on !== 'function') {
            throw new Error('event必须为函数')
          }
          eventObj = on
        }
        else {
          const { route } = this._getCurrentPage(delta + 1, routers)
          url = route
          deltaNums = delta
          paramsObj = params
        }
      }
        break
      case Check.isString(userConfig):
        url = userConfig
        break
      case Check.isNumber(userConfig):
      case typeof userConfig === 'undefined': {
        userConfig = userConfig || 1

        const { route } = this._getCurrentPage(userConfig + 1, routers)
        url = route
        deltaNums = userConfig
      }
        break
      default:
        throw new Error('参数必须是对象，数字，字符串')
    }

    const config = {
      name,
      url,
      delta: deltaNums,
      type,
      encode: this.isEncode ? '1' : '0',
      query: queryObj,
      params: paramsObj,
      uniConfig,
    }

    const currentRoute = this._getCurrentPage(1, routers)
    const { route: fromPath } = currentRoute
    const [toPath] = config.url.split('?')

    config.url = getFullPath(config.url, fromPath) // 根据当前路径的全路径自动补全，将 ./或../或pages开头统一转为/pages/home
    config.toPath = toPath
    config.fromPath = fromPath

    if (this.before && typeof this.before === 'function') {
      const nextRes = await new Promise((next) => {
        const beforeRes = this.before(Object.assign({}, config), next)
        if (!beforeRes) {
          this.isLock = false
        }
      })
      this.isLock = true
      if (Check.isString(nextRes)) {
        config.url = getFullPath(nextRes, fromPath)
        config.toPath = config.url.split('?')[0]
      }
      else if (Check.isObject(nextRes)) {
        for (const key in config) {
          if (nextRes[key]) {
            if (key === 'url') {
              config.url = getFullPath(nextRes.url, fromPath)
              config.toPath = config.url.split('?')[0]
            }
            else {
              config[key] = nextRes[key]
            }
          }
        }
      }
    }

    this.paramsKey = fromPath

    this.pageParams.set(this.paramsKey, !Check.isPlainObject(paramsObj) ? paramsObj : {})

    currentRoute[PAGE_CUSTOM_KEY] = eventObj
    if (typeof eventData !== 'undefined') {
      const func = routers.find(e => e.route === toPath)?.[PAGE_CUSTOM_KEY]
      typeof func === 'function' && func(eventData, toPath, fromPath)
    }
    try {
      const res = await _nav(config)
      if (this.after && typeof this.after === 'function') {
        this.after(res)
      }
      return res
    }
    catch (e) {
      if (this.onError && typeof this.onError === 'function') {
        this.onError(e)
      }
      return e
    }
    finally {
      this.isLock = false
    }
  }

  /**
   *
   * @param {any} eventData 传入event的任意参数
   * @param {string|string[]} pagePath 页面路径，不传默认只触发上一页，传入路径数组则执行多个路径的event
   */
  trigger(eventData, pagePath) {
    eventData = deepClone(eventData || '')
    const routers = this.getPageStack()
    const currentRoute = this._getCurrentPage(1, routers)
    const triggerPagePath = currentRoute?.route

    if (Array.isArray(pagePath)) {
      for (let i = 0; i < pagePath.length; i++) {
        const path = pagePath[i]
        const func = routers.find(e => e.route === path)?.[PAGE_CUSTOM_KEY]
        typeof func === 'function' && func(eventData, pagePath, triggerPagePath)
      }
      return
    }

    const func = this._getCurrentPage(2, routers)?.[PAGE_CUSTOM_KEY]
    typeof func === 'function' && func(eventData, pagePath, triggerPagePath)
  }

  backEmit(...args) {
    if (args.length === 0) {
      return this.back(1)
    }
    else if (args.length === 1) {
      return this._push(1, {}, 'navigateBack', args[0])
    }
    return this._push(args[0], {}, 'navigateBack', args[1])
  }

  get params() {
    // 获取params
    return this.pageParams.get(this.paramsKey) || {}
  }

  /**
   * @param {any} val
   */
  set encode(val) {
    this.isEncode = !!val
  }

  get encode() {
    return this.isEncode
  }

  // 页面栈长度是否只有1
  isFirstPage() {
    return this.getPageStack().length === 1
  }

  // 执行当前页面的某个方法
  callMethod(methodName = '', ...data) {
    if (!methodName) throw new Error('必须输入触发页面的方法名')
    const { $vm } = this._getCurrentPage()

    const method = this._getMethod($vm, methodName)
    method && method(...data)
  }

  _getMethod($vm, methodName) {
    if (!$vm) throw new Error('找不到该页面实例')
    if ($vm[methodName]) return $vm[methodName]
    if ($vm.$.exposed?.[methodName]) return $vm.$.exposed[methodName]
    throw new Error('当前页面没有此方法')
  }

  findPageIns(route) {
    return this.getPageStack().find(e => e.route === route)
  }

  // 执行某个页面的某个方法
  callAnyPageMethod(path = '', methodName = '', ...data) {
    if (!path) throw new Error('必须输入触发页面的路径')
    if (!methodName) throw new Error('必须输入触发页面的方法名')
    if (!methodName.startsWith('public')) throw new Error('被触发方法名必须public开头')

    const { $vm } = this.findPageIns(path)

    const method = this._getMethod($vm, methodName)
    method && method(...data)
  }

  // 执行每个页面的每个方法
  callAnyPageMethods(...params) {
    for (let i = 0; i < params.length; i++) {
      if (!Array.isArray(params[i])) throw new TypeError('参数必须是数组')
      const [path, methodName, ...data] = params[i]
      this.callAnyPageMethod(path, methodName, ...data)
    }
  }

  /**
   * 子页面回退传参执行回退后的页面的某个方法
   * @param methodName 父级回调名称
   * @param delta 返回层数,默认返回上一页
   * @param params 传递给父级的参数
   */
  sonSendToParent(methodName = '', delta = 1, ...params) {
    const pages = this.getPageStack()
    const prevPage = pages[pages.length - (delta + 1)]

    const method = this._getMethod(prevPage.$vm, methodName)
    method && method(...params)

    this.back(1)
  }

  pagePath() {
    return this.currentPage()?.route
  }

  get query() {
    const currentRoute = this.currentPage()

    const options = currentRoute?.options || currentRoute?.$page?.options || {}
    const { qryStr = '{}', encode } = options
    if (encode === '0') {
      const newOptions = Object.assign({}, options)
      delete newOptions.encode
      return paramsToObj(newOptions)
    }
    else {
      if (qryStr === '{}') {
        return {}
      }
      try {
        const decodeParse = JSON.parse(decodeURIComponent(qryStr))
        const newOptions = Object.assign({}, options)
        delete newOptions.qryStr
        return Object.assign({}, newOptions, decodeParse)
      }
      catch (e) {
        console.warn('queryStr使用JSON.parse解析错误，返回原值，注意数据类型解析')
        return options
        // TODO handle the exception
      }
    }
  }

  reloadPage(isTabBar = false) {
    const currPage = this._getCurrentPage() // curpage instance
    isTabBar = currPage?.$page?.meta?.isTabBar || isTabBar // 小程序路由信息中获取不到是否是tabbar需用户自动传入
    const path = currPage.route // pages/test/test
    if (!path) return
    const action = isTabBar ? 'reLaunch' : 'redirectTo'
    this[action]({
      url: path,
      query: this.query,
      params: this.params,
    }, {
      animationType: 'fade-in',
      animationDuration: 200,
    },)
  }
}

let instance = null
/**
 * @class Router
 * @returns {Router}
 */
export default function createRouter(arg) {
  if (instance) return instance
  instance = new Router(arg)
  instance.Router = Router
  instance.UNI_NAV_ACTION = UNI_NAV_ACTION

  return instance
}
